Skip to content

Add README scaffolding to create wizard: default-on toggle, seeded template, authored-content preservation, and atomic write transaction - #457

Merged
eXamadeus merged 1 commit into
mainfrom
julian/07-23-add_readme_scaffolding_to_create_wizard_default-on_toggle_seeded_template_authored-content_preservation_and_atomic_write_with_rollback
Jul 24, 2026
Merged

Add README scaffolding to create wizard: default-on toggle, seeded template, authored-content preservation, and atomic write transaction#457
eXamadeus merged 1 commit into
mainfrom
julian/07-23-add_readme_scaffolding_to_create_wizard_default-on_toggle_seeded_template_authored-content_preservation_and_atomic_write_with_rollback

Conversation

@eXamadeus

@eXamadeus eXamadeus commented Jul 24, 2026

Copy link
Copy Markdown
Member

Why

Tasks 12 and 13.1–13.2 are complete. This adds first-class README scaffolding to facet create: the wizard and headless paths both produce a README.md by default, seeded from the facet name and description, with an explicit opt-out via --no-readme.

Details

README state model (ReadmeState / ScaffoldReadme)
The form tracks a draft with an origin field (seeded | authored). While seeded, identity edits (name, description) automatically regenerate the template. The first explicit edit — either through the in-wizard editor button or the external editor round-trip — flips origin to authored, after which the content is frozen regardless of later identity changes. Toggling README off and back on never discards the draft, so authored content survives a disable/re-enable cycle.

Manifest declaration
An enabled README is declared as an ordinary files: ["README.md"] top-level entry — no README-specific manifest field. The declaration is written last, after asset sections. A disabled README produces no files key.

Atomic scaffold writes (fs-transaction.ts)
writeScaffold previously wrote files sequentially with no rollback. It now builds a list of FsMutation values and applies them through applyFsTransaction, which captures a preimage of every affected path before touching disk, applies each mutation via tmp-then-rename, and restores all preimages in reverse order on any failure. The transaction result is discriminated data for callers that can recover (the future edit apply path); writeScaffold itself throws on failure since create has no fallback.

Wizard editor round-trips
onRequestEditor previously took (section, name, description) positional arguments. It now receives a tagged EditorRequest union (asset-description | readme) so the two editor flows cannot be confused and each arm carries exactly the fields it needs. mergeEditorResult in wizard.tsx dispatches on the tag: asset descriptions are trimmed (single-line semantics); README content is stored verbatim and marked authored.

Headless parity
decideCreate seeds the same readmeTemplate(name, description) the wizard uses, so headless and interactive create never diverge by default. --no-readme (parsed as readme: false) produces { kind: 'disabled' }.

Edit isolation
manifestToFormState sets an inert readme: { enabled: false, draft: { origin: 'seeded', content: '' } } to satisfy the shared FormState shape. The edit flow routes README through its own dedicated panel and is unaffected by this field.

Verification

New and updated unit tests cover: default-enabled README in headless mode, --no-readme opt-out, authored-content freeze across identity edits, disable/re-enable draft preservation, manifest declaration presence and ordering, previewScaffoldFiles ordering, writeScaffold verbatim content, disabled-README omission, and transactional rollback when a write fails mid-batch. CI covers the rest.


Note

Low Risk
Create/scaffold path only; behavior is additive with explicit opt-out and transactional writes reduce partial-failure risk. Edit authoring for supplementary files remains future work.

Overview
Adds default-on README.md scaffolding to facet create, aligned with the non-asset-files work (tasks 13.1–13.2).

Engine: ScaffoldOptions now requires a tagged readme (enabled with verbatim content, or disabled). Enabled READMEs are written as README.md and declared via ordinary top-level files: ["README.md"] (no README-specific manifest field). Shared helpers live in readme.ts (readmeTemplate, path constants). writeScaffold applies manifest + README + assets through new applyFsTransaction (preimage capture, tmp+rename writes, rollback on failure).

CLI: Interactive create gets README toggle, Edit README (external editor), and form state with seeded vs authored drafts—identity edits re-seed only while seeded; authored bytes stay frozen. Headless create seeds the same template by default; --no-readme opts out without forcing headless mode. Editor round-trips use a tagged EditorRequest union (asset-description | readme). Existing e2e scaffolds pass readme: { kind: 'disabled' } where README is irrelevant.

Tests: Engine scaffold/manifest/preview/rollback coverage; headless README defaults; TUI README state tests.

Reviewed by Cursor Bugbot for commit 3f65211. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3f65211

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@eXamadeus, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bf30473-6c6f-4f94-9fe4-2362dbf19eca

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7d938 and 3f65211.

📒 Files selected for processing (19)
  • openspec/changes/support-non-asset-files/tasks.md
  • packages/cli/src/__tests__/create-build.e2e.test.ts
  • packages/cli/src/__tests__/modify.e2e.test.ts
  • packages/cli/src/commands/create/__tests__/headless.test.ts
  • packages/cli/src/commands/create/headless.ts
  • packages/cli/src/commands/create/index.ts
  • packages/cli/src/commands/create/wizard.tsx
  • packages/cli/src/tui/context/__tests__/readme-state.test.tsx
  • packages/cli/src/tui/context/focus-order-context.ts
  • packages/cli/src/tui/context/form-state-context.ts
  • packages/cli/src/tui/views/__tests__/confirm-privacy.test.tsx
  • packages/cli/src/tui/views/create/create-view.tsx
  • packages/cli/src/tui/views/create/wizard.tsx
  • packages/cli/src/tui/views/edit/manifest-to-form.ts
  • packages/engine/src/__tests__/scaffold.test.ts
  • packages/engine/src/fs-transaction.ts
  • packages/engine/src/index.ts
  • packages/engine/src/readme.ts
  • packages/engine/src/scaffold/index.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julian/07-23-add_readme_scaffolding_to_create_wizard_default-on_toggle_seeded_template_authored-content_preservation_and_atomic_write_with_rollback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

eXamadeus commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d301e3786b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (mutation.kind === 'write') {
mkdirSync(dirname(mutation.path), { recursive: true })
// tmp + rename so a reader never observes a half-written file.
const tmp = `${mutation.path}.tmp`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve pre-existing temporary files

Use a unique transaction-owned temporary path (and clean it up) instead of ${mutation.path}.tmp. When facet create --force targets a project that already has, for example, README.md.tmp or facet.json.tmp, this write overwrites that unrelated authored file and the subsequent rename removes it; the transaction never captures or restores temporary-path preimages, so the promised rollback cannot recover the data.

Useful? React with 👍 / 👎.

Comment on lines +242 to +246
if (!result.ok) {
throw new Error(
`scaffold write failed at ${result.failedPath}: ${result.reason}` +
(result.rollback.ok ? '' : ` (rollback incomplete: ${result.rollback.failedPaths.join(', ')})`),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return scaffold failures as a result value

Expose the transaction failure as a discriminated writeScaffold result instead of throwing it. Filesystem failures such as an unwritable target or a conflicting path are expected create outcomes that the command can render and exit from normally; this throw instead bypasses both the headless and wizard command paths and reaches the process-level catch as an “unexpected” exit (code 2).

AGENTS.md reference: AGENTS.md:L262-L266

Useful? React with 👍 / 👎.

Comment thread packages/engine/src/fs-transaction.ts
Comment thread packages/engine/src/fs-transaction.ts
Comment thread packages/cli/src/commands/create/index.ts
Comment thread packages/cli/src/tui/views/create/create-view.tsx
Comment thread packages/engine/src/fs-transaction.ts
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds first-class README.md scaffolding to facet create — both wizard and headless paths produce a README by default, seeded from name and description, with an explicit --no-readme opt-out.

  • README state model (ReadmeState in form-state-context.ts): a draft with a seeded/authored origin guards re-seeding; identity edits regenerate the template while seeded, and the first explicit editor round-trip flips it to authored and freezes it permanently. Toggling README off and back on preserves the draft.
  • Atomic scaffold writes (fs-transaction.ts): writeScaffold now builds FsMutation values and applies them through applyFsTransaction, which captures file preimages, applies via tmp+rename, and restores preimages in reverse order on failure. The EditorRequest union (asset-description | readme) replaces the old positional (section, name, description) arguments, making the two editor flows type-safe and non-conflatable.
  • Headless parity: decideCreate seeds the same readmeTemplate(name, description) the wizard uses; the new required readme field on ScaffoldOptions has no cross-repo impact.

Confidence Score: 5/5

Safe to merge. Changes are well-scoped to facet create scaffolding and the new transactional write path; rollback behavior is tested and correct for the covered scenarios.

The state model, transactional write, headless parity, and editor-round-trip refactor are all implemented correctly and backed by new unit tests. The one noted gap — newly-created subdirectories not being cleaned up on rollback — is a design-level limitation that is benign for the current facet create use case (isolated fresh target directory) and does not leave any user-visible project in a bad state.

packages/engine/src/fs-transaction.ts — the directory-not-rolled-back gap is worth understanding before this module is reused for the future edit apply path, where the target directory may not be fresh.

Reviews (3): Last reviewed commit: "Add README scaffolding to create wizard:..." | Re-trigger Greptile

Comment on lines +91 to +95
const applied: FsMutation[] = []
for (const mutation of mutations) {
try {
applyMutation(mutation)
applied.push(mutation)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead applied tracking array

applied is pushed to on every successful mutation but is never read. rollbackApplied always receives preimages (the full pre-transaction preimage set), not applied, so the tracking does nothing. Restoring every preimage when only a subset was applied is still correct — restoring a preimage for an untouched path is a no-op (rmSync with { force: true } on a path that was never written). The array and its push call can be removed entirely to avoid misleading future readers into thinking it drives rollback selection.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread packages/engine/src/scaffold/index.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3293d10ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

},
]
if (opts.readme.kind === 'enabled') {
mutations.push({ kind: 'write', path: join(targetDir, README_MD), bytes: encoder.encode(opts.readme.content) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid overwriting an existing README without --force

When the target directory has no facet.json but does have a pre-existing README.md (for example, creating a facet in an existing repository root), overwrite protection permits the operation because it checks only facet.json in create/index.ts:85. This new default mutation then silently replaces the authored README even though the user did not pass --force; guard planned output paths or require explicit overwrite consent before writing it.

Useful? React with 👍 / 👎.


function applyMutation(mutation: FsMutation): void {
if (mutation.kind === 'write') {
mkdirSync(dirname(mutation.path), { recursive: true })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove transaction-created directories during rollback

If a later mutation fails after this call has created parent directories—for example, a pre-existing commands file makes the command write fail after a skill was written—the rollback removes the planned files but never removes newly created skills/<name> directories. The target is therefore not restored to its pre-transaction state despite the transaction contract; record which directories were newly created and prune them after restoring preimages.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b3293d1. Configure here.

Comment thread packages/cli/src/commands/create/index.ts

eXamadeus commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • Jul 24, 4:47 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 24, 4:55 AM UTC: Graphite rebased this pull request as part of a merge.
  • Jul 24, 4:55 AM UTC: @eXamadeus merged this pull request with Graphite.

@eXamadeus
eXamadeus changed the base branch from julian/07-23-emit_0.2_build_manifests_with_supplementary_file_support_plan-driven_archive_assembly_and_filesystem-identity_validation to graphite-base/457 July 24, 2026 04:52
@eXamadeus
eXamadeus changed the base branch from graphite-base/457 to main July 24, 2026 04:53
…mplate, authored-content preservation, and atomic write with rollback
@eXamadeus
eXamadeus force-pushed the julian/07-23-add_readme_scaffolding_to_create_wizard_default-on_toggle_seeded_template_authored-content_preservation_and_atomic_write_with_rollback branch from b3293d1 to 3f65211 Compare July 24, 2026 04:54
@eXamadeus
eXamadeus merged commit b9852b9 into main Jul 24, 2026
7 checks passed
@eXamadeus
eXamadeus deleted the julian/07-23-add_readme_scaffolding_to_create_wizard_default-on_toggle_seeded_template_authored-content_preservation_and_atomic_write_with_rollback branch July 24, 2026 04:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f652119c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

skill: { type: 'array', description: 'Skill to scaffold, repeatable (headless mode)' },
agent: { type: 'array', description: 'Agent to scaffold, repeatable (headless mode)' },
command: { type: 'array', description: 'Command to scaffold, repeatable (headless mode)' },
readme: { type: 'boolean', description: 'Scaffold a README.md (default on; pass --no-readme to skip)' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor --no-readme in the wizard

When users run facet create --no-readme without another headless content flag, decideCreate deliberately selects the wizard, but the flag is never passed into its initial form state. The wizard therefore starts with README enabled and will create and declare README.md unless the user manually disables it, despite the advertised --no-readme opt-out. Apply this flag to the wizard state (or make it select an appropriate noninteractive path) as well as the headless options.

Useful? React with 👍 / 👎.

Comment on lines +22 to +24
}, [steps, ctx])
report(ctx.toCreateOptions().readme)
return null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run probe mutations only once

For each test whose steps callback changes form state, including all of the new mutation tests, ctx gets a new identity after the update, so this effect runs again and invokes the setters again. Because each setter returns a new form object even when the values are unchanged, this produces an unbounded render/effect loop rather than a settled result. Depend only on the stable steps callback (or otherwise guard the setup effect) so the probe performs its sequence once.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant